iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Claude AI

資深工程師的 Claude Code 工作筆記系列 第 26 篇

Day 26:權限規則與 sandbox,兩層邊界各擋什麼

  • 分享至 

  • xImage
  •  

Day 25 講了好幾個 Claude 一起做事怎麼不互相踩到。今天換一個問題:就算規矩寫得再清楚,總有 session 會做錯,這時候傷害能不能有上限?上限不能靠提示詞要它小心,要靠 Claude Code 自己或作業系統去執行的邊界。

Claude Code 有兩層這種邊界:權限規則和 sandbox。兩層常被當成同一件事,其實擋的東西完全不同。分不清楚,就會出現「我明明 deny 了,它還是寫進去」的狀況。Day 22 看過一個例子:在 acceptEdits 下只禁編輯工具,Bash 重導照樣能寫檔。今天把這件事講透。

兩層管的東西不一樣

權限規則 sandbox
管誰 工具呼叫:Bash、Read、Edit、WebFetch、MCP 都算 行程:Bash、PowerShell、Monitor 的指令與它們的子行程
比對依據 指令的文字、路徑、網域 作業系統層實際的檔案與網路存取
什麼時候擋 指令執行之前 指令執行當中
預設狀態 沒設規則時依 mode 決定問不問 預設關閉,要自己打開

兩層就是這樣切的:權限控制哪些工具能用、能碰哪些檔案或網域;sandbox 是作業系統層的強制執行,限制 shell 指令的檔案系統與網路存取。強度也不同。權限決策在指令跑之前,依指令字串做出;sandbox 的邊界由作業系統套在執行中的行程上,不管模型選了什麼指令,也不管一個被允許的指令實際做的比名字看起來更多。

與其問哪一層比較強,不如先問:你要擋的這件事,是看得出來的文字,還是看不出來的行為?前者權限規則夠用,後者要靠 sandbox。

權限規則與 sandbox 各管什麼
https://ithelp.ithome.com.tw/upload/images/20261010/20149301ooFEa6RWpt.png

權限規則:比對的是寫出來的指令文字

評審順序

規則由 Claude Code 執行,不是模型。你在提示詞或 CLAUDE.md 裡寫的話會影響 Claude 想做什麼,但不會改變 Claude Code 允許什麼。這是為什麼「一定不能做」的事要寫成規則,不能只寫成叮嚀。

規則依序評審:deny、ask、allow,第一個命中的決定結果,規則寫得多具體不會改變順序。這帶來兩個直接的後果:

  • 寬的 deny(例如 Bash(aws *))會擋掉所有命中的呼叫,包括同時命中窄 allow(Bash(aws s3 ls))的那種。allow 不能在 deny 上開例外。
  • 設定有多個來源時,規則是合併的,不是覆蓋。任何一層 deny 了,其他層都不能 allow。使用者層的 deny 會擋掉專案層的 allow。

組織想要硬保證的事,放在 managed settings 的 deny,這是優先序最高的一層,下面的人改不了。

它比對的是文字,不是程式

Bash 規則比對的是 Claude 寫出來的指令文字,在拆開複合指令、剝掉固定的包裝指令(例如 timeout、nohup)之後。複合指令中,deny 與 ask 只要任何一個子指令命中就生效,包括藏在子 shell 或迴圈裡的;allow 則要每個子指令各自被允許。

但它對「同一個程式用不同形式呼叫」無能為力。常見的對照:

規則 擋得到 擋不到
Bash(curl *) curl https://example.com /usr/bin/curl https://example.com、sh -c 'curl ...'
Bash(rm *) rm -rf build/ /bin/rm -rf build/、bash -c 'rm -rf build/'
Bash(git push *) git push origin main git -C . push origin main、git 'push' origin main

另外,devbox run、npx、docker exec 這類執行器不會被剝掉,規則會比對到它後面的一切。想用規則去限制指令的參數也很脆弱,改個旗標、換成 https、先指派變數再用,都能繞過。

所以結論是:deny 或 ask 規則涵蓋的是 Claude 通常會產生的那種呼叫,不是圍住那個程式的安全邊界。要不依賴指令文字就能擋,用 sandbox;要用自己的邏輯檢查完整指令,用 PreToolUse hook。

Read 與 Edit 的 deny 涵蓋範圍

這兩條規則不只管內建的檔案工具,也管 Claude Code 認得的檔案指令(cat、head、tail、sed、tee)和 Bash 重導向的目標。但不管這種情況:指令沒指名檔案就去讀(例如在含有該檔的目錄執行 grep -r),或是任意子行程間接讀寫檔案(例如一支自己開檔的 Python 或 Node 腳本)。要擋掉所有行程碰某個路徑,要開 sandbox。

mode 改的是「問不問」,不是「deny 有沒有效」

permission mode 決定哪些動作不問就做:default(介面上叫 Manual,default 是設定值)只放行讀取;acceptEdits 再放行工作目錄內的檔案編輯與常見的檔案系統指令;auto 由另一個分類器模型審規則沒涵蓋、也不是一般讀寫的動作;dontAsk 只做讀取與預先核准的,其餘一律拒絕;bypassPermissions 什麼都不問。

幾個和邊界直接相關的事實:

  • deny 在每個 mode 都有效,包括 bypassPermissions(Day 22 提過)。反過來,allow 在 bypassPermissions 裡沒有作用,因為本來就全放行了。
  • 進入 auto 時,過寬的 allow 規則會被丟棄,例如 Bash(*)、Bash(python*) 這類能執行任意程式的 allow。離開 auto 後才恢復。
  • 你在對話中講的邊界,分類器會當作封鎖訊號,但不會存成規則。 對話被壓縮時,那句話可能就沒了。要硬保證,加 deny 規則。對 auto 來說,絕對不能做的事用 managed settings 的 permissions.deny,它在分類器看到之前就擋了。
  • bypassPermissions 對提示注入和意外動作沒有任何保護。 只該在容器、VM 或 dev container 這種隔離環境、而且沒有對外網路的情況下使用。

hook 在前面,但改不了 deny

PreToolUse hook 在權限提示之前執行,exit 2 會擋下工具呼叫(Day 17 講過),而且在規則被評估之前就擋。hook 也可以替沒有規則命中的呼叫省掉提示。但它回傳 allow 並不能蓋掉規則:命中的 deny 規則照擋,命中的 ask 規則照問。

sandbox:圍住行程的邊界

預設是什麼

sandbox 預設關閉,用 /sandbox 或設定打開。它建立在開源的 @anthropic-ai/sandbox-runtime 上,macOS 用 Seatbelt,Linux 與 WSL2 用 bubblewrap。原生 Windows 不支援,指令會在沒有 sandbox 的情況下直接跑。

還有一個很容易漏掉的預設:sandbox 啟動不了的時候,預設是繼續跑,不是停下來。 依賴沒裝、平台不支援,Claude Code 都會照樣執行指令,只是不套 sandbox。想讓它啟動失敗就停,要設 sandbox.failIfUnavailable: true。

打開之後的預設邊界:

存取 預設
寫入 工作目錄、使用者暫存目錄、你額外加入的目錄;受保護路徑維持禁寫
讀取 整台機器的大部分,包括 ~/.ssh 與 ~/.aws/credentials 這類憑證檔
網路 沒有直接的出路,連線都走本機的 proxy,由它逐個 host 比對放行名單,名單一開始是空的
環境變數 從 Claude Code 繼承,包括裡面的任何機密

預設擋寫,不擋讀。 這點最容易被誤會成「sandbox 讓行程看不到機密」。要擋讀取,得自己列出 filesystem.denyRead 或設定憑證保護。也沒有內建的憑證 deny 清單,只有你列出來的檔案與變數才會被限制。

它真正的價值:子行程一體適用

限制掛在作業系統層,所以 sh -c、Python 的 open()、subprocess 這些走不同路徑的寫法,通通受同一套邊界約束。這正是上一節規則比對不到的那塊。寫入被擋的時候,行程看到的是一般的權限錯誤,macOS 是 Operation not permitted,Linux 是 Read-only file system。網路被擋時,Claude Code 會在結果裡寫出被拒絕的 host,讓 Claude 看得見是哪一個。

網路方面,直連沒有路,所以不吃 proxy 環境變數的工具(直接執行的 ssh、多數資料庫驅動)連放行名單內的主機也連不上,UDP、QUIC、ping 同樣出不去。名單外的 host 怎麼處理,要看 mode:default 與 acceptEdits 會問你,auto 拒絕(除非指令自己列出 host 並經分類器核准),dontAsk 拒絕,bypassPermissions 放行。想要一律拒絕、不問,設 strictAllowlist,它只認使用者層、managed settings 與 --settings。

它不管的東西

sandbox 只包住 shell 指令。Claude 的檔案工具也在它外面,但由權限規則管;command hook、本機 MCP server、LSP、statusline、apiKeyHelper 則以完整權限在跑。想全包住,要把整個 Claude Code 行程放進 sandbox runtime、容器或 VM。

也就是說,開了 sandbox 之後,Read 工具照樣讀得到 ~/.ssh,要擋它還是得靠 Read 的 deny 規則。

兩層怎麼接在一起,縫在哪

接點:規則會被併進 sandbox

兩層不是各管各的,有一組設計好的整合:

權限規則 在 sandbox 裡的效果
Edit allow 等同 sandbox.filesystem.allowWrite
Read、Edit deny 併入 denyRead、denyWrite
WebFetch(domain:...) 併入網域的允許與拒絕清單

所以路徑型的 Edit(./config/**) deny,既管檔案工具,也管 sandbox 內的 Bash。這是 Day 22 那個問題的另一種解法:只禁 Edit、Write 這兩個工具名擋不住 Bash 寫檔,要擋就用路徑型的規則,或直接設 sandbox.filesystem.denyWrite。就算沒開 sandbox,路徑型的 Edit deny 也擋得到 > file 這種重導,擋不到的是腳本自己開檔。

反向不成立:denyRead 擋不了 Read 工具,allowedDomains 也限制不了 WebFetch。

縫一:auto-allow 讓 sandbox 內的 Bash 不再問

sandbox.autoAllowBashIfSandboxed 預設是 true:指令在 sandbox 內跑,就自動核准,不問。效果是:在 default 模式下,檔案編輯工具會問你,但同一個檔案用 sandbox 內的 Bash 寫就不問。

這個設計是為了減少提示,合理,但要知道它改變了你的使用體驗:問的次數少了,邊界全部落在 sandbox 本身設得對不對。deny 規則在 auto-allow 下仍然有效,有內容的 ask 規則(例如 Bash(git push *))也仍然會問。這兩層的交界處出過 bug:有版本修過「環境變數前綴放在指令前面時,deny 或 ask 規則在 auto-allow 下被漏掉」。所以版本要保持更新。

縫二:逃生口

指令在 sandbox 內失敗時,Claude 會分析失敗原因,可能用 dangerouslyDisableSandbox 參數重試。誰來核准取決於 mode:default 與 acceptEdits 會問你,auto 由分類器判,dontAsk 拒絕,bypassPermissions 不問。

要留意兩件事:

  • 一條 allow 規則會順便核准重試。 如果你有 Bash(curl *) 的 allow,它也會放行那次在 sandbox 外執行的 curl,沒有提示。
  • 想每次都被問,加一條 ask 規則 Bash(dangerouslyDisableSandbox:true),它的優先序高於命中的 allow。想直接堵死,設 sandbox.allowUnsandboxedCommands: false,Claude Code 就會忽略這個參數。

excludedCommands 是另一個口:列在裡面的指令完全在 sandbox 外跑,沒有檔案限制、沒有 proxy。它跑的是你的完整權限。寫成涵蓋直譯器、工作目錄內的腳本、或會依檔案行動的工具(例如 docker compose 讀它的 compose 檔),Claude 就可能先寫那個檔,再在 sandbox 外執行它。

縫三:網路只看 hostname

proxy 依客戶端提供的 hostname 決定放行,預設不檢查 TLS。放行 github.com 這種大網域,可能留下資料外洩的路徑;sandbox 內的程式也可能用 domain fronting 這類手法連到名單外的 host。有效的 sandbox 同時需要檔案系統與網路隔離,缺一個,就能外洩或植入後門。

兩層之間的縫

沒人在旁邊看的時候

多 session 協作、背景 session、無頭執行,共同點是沒有人在每個提示前按確認。這時候邊界要先設好,因為提示不會替你擋。

  • subagent 沒設 permissionMode 時繼承父對話的 mode。 父對話是 bypassPermissions、acceptEdits 或 auto 時,子代理一律跑一樣的 mode,忽略它自己設定的 permissionMode。sandbox 也是同一套設定。
  • agent teams 的成員沿用 lead 的 mode(dontAsk 除外)。lead 用 --dangerously-skip-permissions,全員都是,這是 Day 25 講過的。別假設每個 teammate 各自在 sandbox 裡,需要隔離就把整個行程放進容器。
  • 背景 session 的提示會停在「需要輸入」,要你 attach 進去才能回答,所以無人看管時它會一直等。要用 bypass 的背景 session,必須先在互動式終端機接受過一次免責提示。
  • 訊息不是授權。 別的 agent 傳來的訊息替你核准不了提示(Day 25 講過)。

對無人值守的建議很明確:sandboxed Bash 只管 shell 指令,單獨用它不足以支撐完全無人值守的執行;auto 的分類器是逐個動作的控制,不是隔離邊界,所以隔離邊界仍然是額外的縱深防禦;而 --dangerously-skip-permissions 則必須搭配容器、VM 或 sandbox runtime 這種隔離環境。

可以這樣配置

下面是一份中性的示意設定,放在專案的 .claude/settings.json,讓 repo 裡的規則跟著專案走。它示範的是分層的想法,不是一份該照抄的清單:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)",
      "Edit(./config/prod/**)"
    ],
    "ask": [
      "Bash(git push *)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["~/.aws"]
    },
    "network": {
      "allowedDomains": ["registry.npmjs.org", "github.com"]
    }
  }
}

這份示意裡每一段都對應前面講的一個縫:

  • permissions.deny 用路徑型規則:擋檔案工具,同時併進 sandbox 的 denyRead 與 denyWrite。代價是 sandbox 內的 Bash,包括 dev server 與測試,也讀不到這些檔,需要它們的指令要改由你在外面跑,或換成不寫進專案的機密注入方式。
  • ask 放對外動作:git push 這類有內容的 ask 規則,在 auto-allow 下仍然會問。但它只攔得到平常寫法的 git push,換成 git -C . push 就繞過去了。網域名單裡有 git host,sandbox 就不會攔推送,所以兩層要一起看。
  • failIfUnavailable:避免 sandbox 起不來時照樣執行指令。要注意它的代價:在原生 Windows,或沒裝 bubblewrap 與 socat 的 Linux 上,Claude Code 會直接起不來。放進專案設定,就會套用到每個開這個 repo 的人,成員環境不一致時,改放 .claude/settings.local.json 或各自的使用者層。
  • allowUnsandboxedCommands: false:堵死逃生口,Claude Code 會忽略 dangerouslyDisableSandbox。想保留逃生口、但每次都問,改成加一條 Bash(dangerouslyDisableSandbox:true) 的 ask 規則。兩種擇一,不必同時用。
  • denyRead 補預設的讀取缺口:預設整台機器都讀得到。要不要連 ~/.ssh 一起擋要看情況,在 Linux 與 WSL2 上,擋了之後 SSH 協定的 git remote 會讀不到金鑰。
  • 網路名單從小開始:每個放行的網域都是潛在的外洩路徑。git push 走 HTTPS 時要連 git host,所以名單裡要有它,這就是名單放得進大網域的代價。macOS 上 SSH remote 在 sandbox 內不通,就算 host 在名單裡也一樣,要改用 HTTPS remote,或用 excludedCommands 把 git 的網路指令拿出來,但那就是前面講的另一個口。想讓名單外一律直接拒、不問,可以設 strictAllowlist,但它只認使用者層、managed settings 與 --settings,開了之後 repo 裡的 allowedDomains 會被忽略,名單要跟著搬到同一層。

選哪一層,可以這樣判斷:

你要擋的是 用什麼
一個看得出來的固定動作(git push、讀 .env) 權限規則
「不管用什麼程式,都不能碰這個路徑」 路徑型的 Read/Edit deny,加上開啟 sandbox(deny 會併進去)
完整指令文字要依自己的邏輯檢查 PreToolUse hook
一定不能發生,組織層級保證 managed settings 的 deny 與 sandbox 鎖
無人看管、要整個行程圈起來 容器、VM 或 sandbox runtime,不只是 sandboxed Bash

依要擋的東西選擇哪一層,不是強弱排序

今天的重點

  • 權限規則比對指令文字,sandbox 圍住實際行程。兩者各管一塊,不是互相替代。
  • 規則依序 deny、ask、allow,任何一層的 deny 都不能被別層 allow。deny 在每個 mode 都有效,包括 bypassPermissions。
  • sandbox 預設關閉、預設擋寫不擋讀、預設起不來就算了。三個預設都要自己決定,不要沿用。
  • 兩層有整合:Edit、Read 規則會併進 sandbox。反向不成立,denyRead 擋不了 Read 工具。
  • 縫在三處:auto-allow 讓 sandbox 內的 Bash 不再問;逃生口與 excludedCommands;網路只看 hostname。
  • 沒人看管時,邊界要事先設好。單靠 sandboxed Bash 撐不起無人值守,要搭配容器、VM 或 sandbox runtime。

上一篇
Day 25:多 session 協作,讓好幾個 Claude 一起做事又不互相踩到
下一篇
Day 27:排程與定期任務,電腦睡著了、session 關了,任務去哪了
系列文
資深工程師的 Claude Code 工作筆記 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言